#pragma once

#include <iostream>
#include <string>
#include <sys/poll.h>
#include "Sock.hpp"
#include "Log.hpp"
#include <cstring>

const static int gport = 8888;
const static int N = 4096;
const static short defatltevent = 0;
typedef struct pollfd type_t;

class PollServer
{

public:
    PollServer(uint16_t port = gport) : port_(port), fdarray_(nullptr)
    {
    }
    void InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        fdarray_ = new type_t[N];
        for (int i = 0; i < N; i++)
        {
            fdarray_[i].fd = defaultfd;
            fdarray_[i].events = defatltevent;
            fdarray_[i].revents = defatltevent;
        }
    }
    void Accepter()
    {
        std::cout << "有一个新链接到来了" << std::endl;
        // 这里再进行Accept会不会被阻塞？不会，因为我们已经检测到对应的listensock_已经有就绪读事件就绪了
        std::string clientip;
        uint16_t clientport;
        int sock = listensock_.Accept(&clientip, &clientport);
        if (sock < 0)
            return;
        // 得到了对应的sock，我们可不可以进行read/recv，读取sock? 不能
        // 虽然已经得到了新的fd，但是你怎么知道sock上有数据就绪了？
        // 我把你连接建立好，但是我不给你发数据，那么此时read/recv，该套接字上的数据就一直是不就绪的，read/recv就被阻塞了，这里被阻塞，不就是整个进程被阻塞了
        // 所以我们需要将sock交给select，让select进行管理

        // 我们未来不断有listensock_在就绪，在就绪过程中，文件描述符会变多，变多之后，
        // 把对应的文件描述符交给select的本质，就是把他设置进rfds，不管怎么设置进rfds，这是一个输入输出型参数，
        // 当select返回的时候，rfds的位图大部分会被清空，
        // 你怎么保证我们对应的一个套接字在下下次再进行select的时候，依旧是让select监听呢？
        // 因为我们对一个文件描述符，有10个连接挂上了，可能当前只有一个就绪了，那么剩下9个还要select给我关心，对端在未来的某个时间点还要发数据
        // 你怎么保证你要对特定的文件描述符要有持续的监控能力呢？
        // 所以有了新功能，select服务器，使用的时候，需要程序员自己维护一个第三方数组，来进行对已经获得的sock进行管理

        logMessage(Debug, "[%s:%d],sock: %d", clientip.c_str(), clientport, sock);
        // 要让select，帮我们进行关心，只要把sock添加到fdarray_[]里面即可
        int pos = 1;
        for (; pos < N; pos++)
        {
            if (fdarray_[pos].fd == defaultfd)
                break;
        }
        if (pos >= N)
        {
            close(sock);//poll -> 不够了，可以动态扩容，扩容失败了，再close
            logMessage(Warning, "sockfd array[] full");
        }
        else
        {
            fdarray_[pos].fd = sock;
            fdarray_[pos].events = POLLIN;//(POLLIN | POLLOUT)
            fdarray_[pos].revents = defatltevent;
        }
    }

    // echo server
    void HandlerEvent()
    {
        for (int i = 0; i < N; i++) // 检测是否合法
        {
            int fd = fdarray_[i].fd;
            short revent = fdarray_[i].revents;
            if (fd == defaultfd)
                continue;
            if (fd == listensock_.Fd() && (revent & POLLIN)) // 必须是listensock_套接字而且读事件已经就绪，才给我处理
            {
                Accepter(); // 新连接到来的时候，就处理这个新连接，Accepter
            }
            else if (fd != listensock_.Fd() && (revent & POLLOUT)) // 到这里，说明不是listensock_套接字但是是合法的套接字才检测对应的套接字有没有就绪
            {
                // 普通文件描述符就绪
                //  ServiceIO();//暂时不弄他
                char buffer[1024];
                ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0); // 读取会被阻塞吗？
                // 不会，在这里读取的时候，读取一次，一定不会被阻塞，已经有当前事件就绪了
                if (s > 0)
                {
                    buffer[s - 1] = 0;
                    std::cout << "client# " << buffer << std::endl;
                    fdarray_[i].events |= POLLOUT;//改成写，添加这行就行了

                    // 发送回去,也要被select管理的
                    std::string echo = buffer;
                    echo += " [selsect server echo] ";
                    send(fd, echo.c_str(), echo.size(), 0);
                }
                else
                {
                    if (s == 0)
                    {
                        logMessage(Info, "client quit..., fdarray[i] -> defaultfd: %d->%d", fd, defaultfd);
                    }
                    else

                        logMessage(Warning, "recv error, client quit..., fdarray[i] -> defaultfd: %d->%d", fd, defaultfd);
                    close(fd);
                    fdarray_[i].fd = defaultfd;
                    fdarray_[i].events = defatltevent;//不想其他干扰你
                    fdarray_[i].revents = defatltevent;
                }
            }
        }
    }

    void Start()
    {
        // 1、这里我们能够直接获取新的链接吗？
        // 2、最开始的时候，我们的服务器是没有太多的sock的，甚至只有一个sock，listensock
        // 3、在网络当中，新连接到来被当做读事件就绪，新连接来了，文件描述符才能越来越多

        // demo1版
        fdarray_[0].fd = listensock_.Fd();
        fdarray_[0].events = POLLIN;
        while (true)
        {
            int timeout = 1000;
            int n = poll(fdarray_, N, timeout); // fdarray_内容管理，合法fd，event全部放入到fdarray_最左侧
            switch (n)
            {
            case 0:
                logMessage(Debug, "timeout, %d: %s", errno, strerror(errno));
                break;
            case -1:
                logMessage(Warning, "%d: %s", errno, strerror(errno));
                break;
            default:
                // 成功了
                logMessage(Debug, "有一个就绪事件发生了: %d", n);
                HandlerEvent();
                DebugPrint();
                break;
            }
        }
    }
    void DebugPrint()
    {
        std::cout << "fdarray[]: ";
        for (int i = 0; i < N; i++)
        {
            if (fdarray_[i].fd == defaultfd)
                continue;
            std::cout << fdarray_[i].fd << " ";
        }
        std::cout << "\n";
    }
    ~PollServer()
    {
        listensock_.Close();
        if (fdarray_)
            delete[] fdarray_;
    }

private:
    uint16_t port_;
    Sock listensock_;
    type_t *fdarray_; // 管理所以的fd
};